using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows;

namespace MalRec.Base.ExceptionHandling
{
    public class ExceptionHelper
    {
        #region private members

        /// <summary>
        /// Flag wether the exception helper is initialized or not
        /// </summary>
        private static bool _isInitialized;

        /// <summary>
        /// The exception configuration
        /// </summary>
        private static ExceptionConfiguration _exceptionConfiguration;

        /// <summary>
        /// The configured exception policies
        /// </summary>
        private static List<ExceptionPolicy> _configuredExceptionPolicies;

        #endregion private members

        #region public members

        /// <summary>
        /// defines possible propagates
        /// </summary>
        [Flags]
        public enum Propagate
        {
            File = 1,
            Database = 2,
            Show = 4
        }

        #endregion public members

        #region constructors

        #endregion constructors

        #region private methods

        /// <summary>
        /// Writes the errorinformation into the configured database
        /// </summary>
        /// <param name="errorMessage">the errormessage to trace</param>
        private static void TraceErrorToDb(string errorMessage)
        {
            if (!_isInitialized)
            {
                Initialize();
            }

            if (!String.IsNullOrEmpty(_exceptionConfiguration.TraceDbTableName) && !String.IsNullOrEmpty(_exceptionConfiguration.TraceDbConnectionString))
            {
                string sqlStatement = String.Format("INSERT INTO {0} (Message) VALUES (@message)", _exceptionConfiguration.TraceDbTableName);
                using (SqlConnection connection = new SqlConnection(_exceptionConfiguration.TraceDbConnectionString))
                {
                    SqlCommand cmd = new SqlCommand(sqlStatement, connection);

                    SqlParameter paramater = new SqlParameter("@message", errorMessage);
                    cmd.Parameters.Add(paramater);

                    connection.Open();
                    cmd.ExecuteNonQuery();
                }
            }
        }

        /// <summary>
        /// Shows a error
        /// </summary>
        /// <param name="errorMessage">the errormessage to show</param>
        /// <param name="ex">the exception as error source</param>
        private static void ShowError(string errorMessage, Exception ex)
        {
            if (!_isInitialized)
            {
                Initialize();
            }

            if (String.IsNullOrEmpty(_exceptionConfiguration.DialogType))
            {
                MessageBox.Show(errorMessage, Properties.Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error);
            }
            else
            {
                //  use the configured error dialog type
                Assembly assembly = Assembly.GetExecutingAssembly();
                Type dialogType = assembly.GetType(_exceptionConfiguration.DialogType);
                if (dialogType == null)
                {
                    MessageBox.Show(errorMessage, Properties.Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error);
                }
                else
                {
                    IExceptionDialog dialog = Activator.CreateInstance(dialogType) as IExceptionDialog;
                    if (dialog == null)
                    {
                        MessageBox.Show(errorMessage, Properties.Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error);
                    }
                    else
                    {
                        dialog.Exception = ex;
                        dialog.Message = errorMessage;
                        dialog.Show();
                    }
                }
            }
        }

        /// <summary>
        /// Gets an exception information stack
        /// </summary>
        /// <param name="ex">the exception to trace</param>
        private static string GetExceptionInformation(Exception ex)
        {
            StringBuilder sb = new StringBuilder();
            if (ex != null)
            {
                sb.AppendLine("Tracing Exception...");
                sb.AppendLine(ex.Message);

                if (ex.StackTrace != null)
                {
                    sb.AppendLine(ex.StackTrace);
                }

                if (ex.Source != null)
                {
                    sb.AppendLine(ex.Source);
                }

                if (ex.Data != null && ex.Data.Count > 0)
                {
                    sb.AppendLine("Exception Data");
                    foreach (DictionaryEntry entry in ex.Data)
                    {
                        sb.AppendLine(string.Format("{0}: {1}", entry.Key, entry.Value));
                    }
                }

                if (ex.InnerException != null)
                {
                    sb.AppendLine("InnerException");
                    sb.AppendLine(ex.InnerException.Message);

                    if (ex.InnerException.StackTrace != null)
                    {
                        sb.AppendLine(ex.InnerException.StackTrace);
                    }

                    if (ex.InnerException.Source != null)
                    {
                        sb.AppendLine(ex.InnerException.Source);
                    }

                    if (ex.InnerException.Data != null && ex.InnerException.Data.Count > 0)
                    {
                        sb.AppendLine("Exception Data");
                        foreach (DictionaryEntry entry in ex.InnerException.Data)
                        {
                            sb.AppendLine(string.Format("{0}: {1}", entry.Key, entry.Value));
                        }
                    }
                }
                sb.AppendLine("Exception traced.");
            }
            return sb.ToString();
        }

        #endregion private methods

        #region protected methods

        #endregion protected methods

        #region public methods

        /// <summary>
        /// Initializes the ExceptionHelper
        /// </summary>
        public static void Initialize()
        {
            try
            {
                _configuredExceptionPolicies = (List<ExceptionPolicy>)ConfigurationManager.GetSection("ExceptionHandling/ExceptionPolicies");

                _exceptionConfiguration = (ExceptionConfiguration)ConfigurationManager.GetSection("ExceptionHandling/Configuration");
                if (_exceptionConfiguration == null) return;
                if (_exceptionConfiguration.UseDefaultConnectionString || String.IsNullOrEmpty(_exceptionConfiguration.TraceDbConnectionString))
                {
                    // Todo: get the connection string from the service
                    //_exceptionConfiguration.TraceDbConnectionString = UserRights.GetUserRights().ConnectionString;
                }

                if (!String.IsNullOrEmpty(_exceptionConfiguration.TraceFileName))
                {
                    var listener = new TextWriterTraceListener(_exceptionConfiguration.TraceFileName);
                    listener.TraceOutputOptions |= TraceOptions.Callstack;
                    Trace.AutoFlush = true;
                    Trace.Listeners.Add(listener);
                }

                _isInitialized = true;
            }
            catch (Exception exception)
            {
                Trace.WriteLine(GetExceptionInformation(exception));
            }
        }

        /// <summary>
        /// Handles a exception
        /// </summary>
        /// <param name="ex">the exception to handle</param>
        /// <param name="propagation">specifies the exception propagation, defined by <see cref="ExceptionHelper.Propagate"/></param>
        /// <returns>true if the exception should be rethrown</returns>
        public static bool Handle(Exception ex, int propagation)
        {
            return Handle(ex, propagation, null);
        }

        /// <summary>
        /// Handles a exception
        /// </summary>
        /// <param name="ex">the exception to handle</param>
        /// <param name="propagation">specifies the exception propagation, defined by <see cref="ExceptionHelper.Propagate"/></param>
        /// <param name="customErrorMessage">a custom error message</param>
        /// <returns>true if the exception should be rethrown</returns>
        public static bool Handle(Exception ex, int propagation, string customErrorMessage)
        {
            try
            {
                if (!_isInitialized)
                {
                    Initialize();
                }

                if ((propagation & (int)Propagate.File) == (int)Propagate.File)
                {
                    if (String.IsNullOrEmpty(customErrorMessage))
                    {
                        Trace.WriteLine(String.Format("{0}: {1}", DateTime.Now, GetExceptionInformation(ex)));
                    }
                    else
                    {
                        Trace.WriteLine(String.Format("{0}: {1}", DateTime.Now, customErrorMessage));
                        Trace.WriteLine(GetExceptionInformation(ex));
                    }
                }

                if ((propagation & (int)Propagate.Show) == (int)Propagate.Show)
                {
                    if (String.IsNullOrEmpty(customErrorMessage))
                    {
                        ShowError(ex.Message, ex);
                    }
                    else
                    {
                        ShowError(customErrorMessage, ex);
                    }
                }

                if ((propagation & (int)Propagate.Database) == (int)Propagate.Database)
                {
                    if (String.IsNullOrEmpty(customErrorMessage))
                    {
                        TraceErrorToDb(GetExceptionInformation(ex));
                    }
                    else
                    {
                        TraceErrorToDb(String.Format("{0}\n{1}", customErrorMessage, GetExceptionInformation(ex)));
                    }
                }
            }
            catch (Exception exception)
            {
                Trace.WriteLine(GetExceptionInformation(exception));
            }

            return true;
        }

        /// <summary>
        /// Handles a exception defined by a configured policy
        /// </summary>
        /// <param name="ex">the exception to handle</param>
        /// <param name="exceptionPolicyName">the exception policy name</param>
        /// <returns>true if the exception should be rethrown</returns>
        public static bool Handle(Exception ex, String exceptionPolicyName)
        {
            return Handle(ex, exceptionPolicyName, null);
        }

        /// <summary>
        /// Handles a exception defined by a configured policy
        /// </summary>
        /// <param name="ex">the exception to handle</param>
        /// <param name="exceptionPolicyName">the exception policy name</param>
        /// <param name="customErrorMessage">a custom error message</param>
        /// <returns>true if the exception should be rethrown</returns>
        public static bool Handle(Exception ex, String exceptionPolicyName, String customErrorMessage)
        {
            try
            {
                if (!_isInitialized)
                {
                    Initialize();
                }

                if (!String.IsNullOrEmpty(exceptionPolicyName))
                {
                    var result = from policy in _configuredExceptionPolicies where policy.Name == exceptionPolicyName.ToLower() select policy;
                    if (result.Count() > 0)
                    {
                        ExceptionPolicy policy = result.First();
                        Handle(ex, policy.Propagation, customErrorMessage);

                        return policy.Rethrow;
                    }
                }
            }
            catch (Exception exception)
            {
                Trace.WriteLine(GetExceptionInformation(exception));
            }

            return true;
        }

        #endregion public methods
    }
}